package org.databandtech.mockapi;

import static org.mockserver.integration.ClientAndServer.startClientAndServer;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.databandtech.mockapi.entity.MockInstance;
import org.mockserver.integration.ClientAndServer;
import org.mockserver.model.Body;
import org.mockserver.model.Cookie;
import org.mockserver.model.Header;
import org.mockserver.model.JsonBody;
import org.mockserver.model.Parameter;
import org.mockserver.model.StringBody;

public class App {

	static ClientAndServer mockServer;
	static int PORT = 7821;
	static String CONNSTR = "jdbc:mysql://localhost:3307/databand?useUnicode=true&characterEncoding=utf-8&useSSL=false";
	static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";// 8.0+ "com.mysql.cj.jdbc.Driver"
	static String DBUSER = "root";
	static String DBPASSWD = "mysql";
	static int MAXID = 0;

	public static void main(String[] args) {
		ConfigRead();
		// 动态实现mock装配
		mockServer = startClientAndServer(PORT);
		List<MockInstance> mockMockInstances = getDataFromDb(0);
		// List<MockInstance> mockMockInstances =
		// getDataFromJsonConf("conf/databand_mockinstances.json");//不用数据载入实例，使用静态配置文件
		mockInstancesAssembling(mockMockInstances);

		System.out.println("Mock启动：http://localhost:" + PORT);
		System.out.println("可运行:");
		for (MockInstance instance : mockMockInstances) {
			System.out.println(instance.getDescribe());
		}

		// 该线程用于监控是否有新的规则生成
		ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
		MockAppendTask worker = new MockAppendTask();
		// 每30秒一次,查询有无新的规则入库
		scheduledThreadPool.scheduleAtFixedRate(worker, 5, 30, TimeUnit.SECONDS);

		// 固化代码实现mock配置
		// StaticMock.mockPost(mockServer);
	}

	private static void ConfigRead() {
		Properties prop = new Properties();
		InputStream inputStream = null;
		try {
			prop.load(new FileInputStream("res/mysql.properties"));
			if (prop != null) {
				PORT = Integer.parseInt(prop.getProperty("mockserverport"));
				CONNSTR = prop.getProperty("connstr");
				DBUSER = prop.getProperty("dbuser");
				DBPASSWD = prop.getProperty("dbpasswd");
				System.out.println("配置文件已载入");
			}

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static List<MockInstance> getDataFromDb(int fromId) {
		List<MockInstance> databand_mockinstances = new ArrayList<MockInstance>();

		Statement stat = null;
		ResultSet rs = null;
		Connection con = null;
		try {
			Class.forName(JDBC_DRIVER);
			con = DriverManager.getConnection(CONNSTR, DBUSER, DBPASSWD);

			String sql = "select * from databand_mockinstances where id>" + fromId;
			stat = con.createStatement();
			rs = stat.executeQuery(sql);
			// 为空map留用
			Map<String, String> nullMap = new HashMap<String, String>();

			while (rs.next()) {
				MAXID = rs.getInt("id");
				// 数据行
				MockInstance inst = new MockInstance();
				inst.setDescribe(rs.getString("descrb"));
				inst.setMethod(rs.getString("method"));
				inst.setPath(rs.getString("path"));
				if (rs.getString("path_parameters") != null) {
					Parameter[] paths = splitParams(rs.getString("path_parameters"));
					inst.setPath_parameters(paths);
				}

				if (rs.getString("query_string_parameters") != null) {
					Parameter[] paths = splitParams(rs.getString("query_string_parameters"));
					inst.setQuery_string_parameters(paths);
				}

				if (rs.getString("req_cookie") == null) {
					inst.setReq_cookie(nullMap);
				} else {
					inst.setReq_cookie(splitMap(rs.getString("req_cookie")));
				}

				if (rs.getString("req_headers") == null) {
					inst.setReq_headers(nullMap);
				} else {
					inst.setReq_headers(splitMap(rs.getString("req_headers")));
				}

				inst.setReq_jsonbody(rs.getString("req_jsonbody"));

				if (rs.getString("resp_cookie") == null) {
					inst.setResp_cookie(nullMap);
				} else {
					inst.setResp_cookie(splitMap(rs.getString("resp_cookie")));
				}

				if (rs.getString("resp_headers") == null) {
					inst.setResp_headers(nullMap);
				} else {
					inst.setResp_headers(splitMap(rs.getString("resp_headers")));
				}

				inst.setResp_statuscode(Integer.parseInt(rs.getString("resp_statuscode")));
				inst.setResp_body(rs.getString("resp_body"));
				// inst.setResp_body("{}");
				databand_mockinstances.add(inst);
			}

		} catch (Exception e) {
			e.printStackTrace();
			MAXID = MAXID - 1;
			System.out.println("MAXID:" + MAXID);

		} finally {
			if (con != null) {
				try {
					con.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if (stat != null) {
				try {
					stat.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if (rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}

		return databand_mockinstances;
	}

	/**
	 * 组装参数数组,使用了特殊的数组分隔符$$$
	 * 
	 * @param rsStr
	 * @return
	 * @throws SQLException
	 */
	private static Parameter[] splitParams(String rsStr) throws SQLException {
		StringTokenizer st1 = new StringTokenizer(rsStr, "$$$");
		Parameter[] paths = new Parameter[st1.countTokens()];
		int pindex = 0;
		while (st1.hasMoreElements()) {
			String trimStr = trimHeadAndTail(st1.nextElement().toString()).trim();
			StringTokenizer st2 = new StringTokenizer(trimStr, "<=>");
			int index2 = 0;
			String key = null;
			String value = null;
			while (st2.hasMoreElements()) {
				if (index2 == 0) {
					key = st2.nextElement().toString();
				} else {
					value = st2.nextElement().toString();
				}
				index2++;
			}
			if (value.startsWith("{") && !value.endsWith("}")) {
				value += "}";
			}
			paths[pindex] = new Parameter(key, value);
			pindex++;
		}

		return paths;
	}

	private static Map<String, String> splitMap(String rsStr) throws SQLException {
		String[] pathParamsArr = rsStr.split(",");

		Map<String, String> map = new HashMap<String, String>();
		for (String str : pathParamsArr) {
			String trimStr = trimHeadAndTail(str, "{", "}").trim();
			String[] trimStrArr = trimStr.split(":");
			map.put(trimStrArr[0], trimStrArr[1]);
		}
		return map;
	}

	public static void mockInstancesAssembling(List<MockInstance> databand_mockinstances) {

		for (MockInstance inst : databand_mockinstances) {
			List<Cookie> cookies = new ArrayList<Cookie>();
			List<Header> headers = new ArrayList<Header>();
			List<Cookie> resp_cookies = new ArrayList<Cookie>();
			List<Header> resp_headers = new ArrayList<Header>();
			// 注入MOCK
			injectionMock(inst, cookies, headers, resp_cookies, resp_headers);
		}
	}

	private static void injectionMock(MockInstance inst, List<Cookie> cookies, List<Header> headers,
			List<Cookie> resp_cookies, List<Header> resp_headers) {
		for (Entry<String, String> entry : inst.getReq_cookie().entrySet()) {
			cookies.add(new Cookie(entry.getKey(), entry.getValue()));
		}
		for (Entry<String, String> entry : inst.getReq_headers().entrySet()) {
			headers.add(new Header(entry.getKey(), entry.getValue()));
		}

		for (Entry<String, String> entry : inst.getResp_cookie().entrySet()) {
			resp_cookies.add(new Cookie(entry.getKey(), entry.getValue()));
		}
		for (Entry<String, String> entry : inst.getResp_headers().entrySet()) {
			resp_headers.add(new Header(entry.getKey(), entry.getValue()));
		}

		if (inst.getPath_parameters() == null) {
			mockQueryParams(inst, cookies, headers, resp_cookies, resp_headers);
		} else {
			mockPathParams(inst, cookies, headers, resp_cookies, resp_headers);
		}
	}

	/**
	 * 目前只支持StringBody和JsonBody
	 */
	private static void mockPathParams(MockInstance inst, List<Cookie> cookies, List<Header> headers,
			List<Cookie> resp_cookies, List<Header> resp_headers) {

		@SuppressWarnings("rawtypes")
		Body reqBody = null;

		if (inst.getReq_jsonbody() == null) {
			inst.setReq_jsonbody("");
		}

		if (inst.getReq_jsonbody().startsWith("{")) {
			reqBody = new JsonBody(inst.getReq_jsonbody());
		} else {
			reqBody = new StringBody(inst.getReq_jsonbody());
		}

		mockServer
				.when(request().withMethod(inst.getMethod()).withPath(inst.getPath())
						.withPathParameters(inst.getPath_parameters())
						// .withQueryStringParameters(inst.getPath_parameters())
						.withCookies(cookies).withHeaders(headers).withBody(reqBody))
				.respond(response().withStatusCode(inst.getResp_statuscode()).withHeaders(resp_headers)
						.withCookies(resp_cookies).withBody(inst.getResp_body()));
		// .withBody(new JsonBody(inst.getResp_body())));
	}

	private static void mockQueryParams(MockInstance inst, List<Cookie> cookies, List<Header> headers,
			List<Cookie> resp_cookies, List<Header> resp_headers) {
		@SuppressWarnings("rawtypes")
		Body reqBody = null;

		if (inst.getReq_jsonbody() == null) {
			inst.setReq_jsonbody("");
		}

		if (inst.getReq_jsonbody().startsWith("{")) {
			reqBody = new JsonBody(inst.getReq_jsonbody());
		} else {
			reqBody = new StringBody(inst.getReq_jsonbody());
		}

		mockServer.when(request().withMethod(inst.getMethod()).withPath(inst.getPath())
				// .withPathParameters(inst.getPath_parameters())
				.withQueryStringParameters(inst.getQuery_string_parameters()).withCookies(cookies).withHeaders(headers)
				.withBody(reqBody))
				.respond(response().withStatusCode(inst.getResp_statuscode()).withHeaders(resp_headers)
						.withCookies(resp_cookies).withBody(inst.getResp_body()));
	}

	public static String trimHeadAndTail(String srcStr, String head, String tail) {
		String result = "";
		if (srcStr.startsWith(head))
			result = srcStr.substring(1);
		if (result.endsWith(tail))
			result = result.substring(0, result.indexOf(tail));
		return result;
	}

	public static String trimHeadAndTail(String srcStr) {
		String result = srcStr.substring(1, srcStr.length() - 1);
		return result;
	}

}
